package com.white.commonlib.manager.net;

import android.os.Handler;
import android.os.Looper;

import com.google.gson.Gson;
import com.white.commonlib.CommonError;
import com.white.commonlib.ApiException;
import com.white.commonlib.engine.GlobalErrorInflater;
import com.white.commonlib.manager.LogManager;
import com.white.commonlib.manager.ThreadManager;
import com.white.commonlib.utils.GsonUtil;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Created by yutf on 2017/9/5 0005.
 */

public class HttpManager
{
    private static HttpManager instance;

    private final static int CONNECT_TIME = 10000;
    private final static int READ_TIME = 10000;

    private final static String POST = "POST";
    private final static String GET = "GET";

    private Gson mGson;
    private Handler mHandler;

    private HttpManager()
    {
        mGson = GsonUtil.getGson();
        mHandler = new Handler(Looper.getMainLooper());
    }

    public static HttpManager getInstance()
    {
        if (instance == null)
        {
            synchronized (HttpManager.class)
            {
                if (instance == null)
                {
                    instance = new HttpManager();
                }
            }
        }
        return instance;
    }

    private class Builder
    {
        private Map<String, String> header;
        private URL mUrl;
        private String method;

        public Builder(String url)
        {
            try
            {
                mUrl = new URL(url);
            } catch (MalformedURLException e)
            {
                throw new ApiException(CommonError.HTTP_URL_ERROR, e.getMessage());
            }
            method = POST;
            header = new HashMap<>();
            initBaseHeader();
        }

        public Builder setMethod(String method)
        {
            this.method = method;
            return this;
        }

        /***
         *初始化基本头数据
         */
        private void initBaseHeader()
        {
            header.put("Accept", "*/*");
            header.put("Accept-Charset", "UTF-8");
            header.put("Charset", "UTF-8");
//            header.put("connection", "Keep-Alive");
            header.put("Accept-Language", "zh-CN");
            header.put("User-Agent", "Android");
            header.put("Content-Type", "application/x-www-form-urlencoded");
        }

        /****
         * 添加请求头
         * 不要和setHeaderMap混用
         * @param key
         * @param value
         * @return
         */
        public Builder addHeader(String key, String value) throws ApiException
        {
            if(key == null || value == null)
                throw new ApiException(CommonError.HTTP_PARAM_ERROR, CommonError.INNER_ERROR_MSG);
            header.put(key, value);
            return this;
        }

        /***
         * 添加请求头
         * @param header    请求头map
         * @return
         */
        public Builder setHeaderMap(Map<String, String> header)
        {
            if(header != null && header.size() > 0)
            {
                Set<Map.Entry<String,String>> set = header.entrySet();
                for(Iterator<Map.Entry<String,String>> it = set.iterator();it.hasNext();)
                {
                    Map.Entry<String, String> i = it.next();
                    String key = i.getKey();
                    String value = i.getValue();
                    if(key == null)
                        throw new ApiException(CommonError.HTTP_HEADER_ERROR, CommonError.INNER_ERROR_MSG);
                    if(value == null)
                        throw new ApiException(CommonError.HTTP_HEADER_ERROR, "The value corresponding to key "+ key +" is null in header");
                }
                this.header.putAll(header);
            }
            return this;
        }

        public HttpURLConnection build() throws IOException
        {
            HttpURLConnection connection  = getDefaultConnection();

            if(header != null && header.size() > 0)
            {
                Set<Map.Entry<String,String>> set = header.entrySet();
                for(Iterator<Map.Entry<String,String>> it = set.iterator();it.hasNext();)
                {
                    Map.Entry<String, String> i = it.next();
                    String key = i.getKey();
                    String value = i.getValue();
                    connection.setRequestProperty(key, value);
                }
            }
            return connection;
        }

        private HttpURLConnection getDefaultConnection() throws IOException
        {
            HttpURLConnection connection  = (HttpURLConnection) mUrl.openConnection();
            connection.setConnectTimeout(CONNECT_TIME);
            connection.setReadTimeout(READ_TIME);
            connection.setRequestMethod(method);
            //设置运行输入,输出:
            connection.setDoOutput(true);
            connection.setDoInput(true);
            //Post方式不能缓存,需手动设置为false
            connection.setUseCaches(false);
            return connection;
        }
    }

    public void doGet(final String url, final ResultCallback<? extends Object> callback)
    {
        ThreadManager.getInstance().get()
                .execute(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        HttpURLConnection conn = null;
                        try
                        {
                            conn = new Builder(url)
                                    .setMethod(GET)
                                    .build();
                            int code = conn.getResponseCode();
                            if (code == HttpURLConnection.HTTP_OK)
                            {
                                // 获取响应的输入流对象
                                InputStream is = conn.getInputStream();
                                String string = StreamUtils.read(is);
//                                LogManager.e(string);
                                handleResponse(string, callback);
                            }else
                            {
                                failedResponse(new ApiException(code, CommonError.HTTP_ERROR_MSG), callback);
                            }
                        } catch (IOException e)
                        {
                            failedResponse(new ApiException(CommonError.HTTP_CONNECTION_ERROR,
                                    CommonError.HTTP_ERROR_MSG), callback);
                        }finally
                        {
                            if(conn != null)
                            {
                                conn.disconnect();
                            }
                        }
                    }
                });
    }

    public void doPost(final String url, final Map<String, String> param,
                       final ResultCallback<? extends Object> callback)
    {
        doPost(url, param, null, callback);
    }

    /***
     * post请求
     * @param url
     * @param param
     * @param header
     * @param callback
     */
    public void doPost(final String url, final Map<String, String> param, final Map<String, String> header,
                       final ResultCallback<? extends Object> callback)
    {
        ThreadManager.getInstance().get()
                .execute(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        HttpURLConnection conn = null;
                        try
                        {
                            conn = new Builder(url)
                                            .setHeaderMap(header)
                                            .build();
                            writeParam(url, conn.getOutputStream(), param);
                            int code = conn.getResponseCode();
                            if (code == HttpURLConnection.HTTP_OK)
                            {
                                // 获取响应的输入流对象
                                InputStream is = conn.getInputStream();
                                String string = StreamUtils.read(is);
                                LogManager.e("response：" + string);
                                handleResponse(string, callback);
                            }else
                            {
                                failedResponse(new ApiException(code, CommonError.HTTP_ERROR_MSG), callback);
                            }
                        } catch (IOException e)
                        {
                            failedResponse(new ApiException(CommonError.HTTP_CONNECTION_ERROR,
                                    CommonError.HTTP_ERROR_MSG), callback);
                        }finally
                        {
                            if(conn != null)
                            {
                                conn.disconnect();
                            }
                        }
                    }
                });

    }

    private void handleResponse(String string, final ResultCallback<? extends Object> callback)
    {
        if (callback.mType == String.class)
        {
            successResponse(string, callback);
        } else
        {
            //如果需要验证返回数据的正确性,会进行两次转换。
            if(callback.needVarifyResult())
            {
                try
                {
                    final JSONObject obj = new JSONObject(string);
                    if(callback.isSuccess(obj))
                    {
                        Object o = mGson.fromJson(string, callback.mType);
                        successResponse(o, callback);
                    }else
                    {
                        mHandler.post(new Runnable()
                        {
                            @Override
                            public void run()
                            {
                                try
                                {
                                    int erCode = callback.getServerCode(obj);
                                    callback.onError(callback.getServerFailedMessage(obj),erCode, new Exception("服务器错误"));
                                } catch (JSONException e)
                                {
                                    e.printStackTrace();
                                }
                            }
                        });
                    }
                } catch (JSONException e)
                {
                    e.printStackTrace();
                    failedResponse(e, callback);
                }
            }else
            {
                //不验证，直接按输入类型转换对象
                Object o = mGson.fromJson(string, callback.mType);
                successResponse(o, callback);
            }
        }
    }

    private void writeParam(String host, OutputStream out, Map<String, String> param) throws IOException
    {
        if(param != null && param.size() > 0)
        {
            String and = "&";
            StringBuffer sb = new StringBuffer();
            for (Map.Entry<String, String> entry : param.entrySet())
            {
                String key = entry.getKey();
                String value = entry.getValue();
                if(key == null)
                    throw new ApiException(CommonError.HTTP_PARAM_ERROR, CommonError.INNER_ERROR_MSG);
                if(value == null)
                    throw new ApiException(CommonError.HTTP_PARAM_ERROR,
                            "The value corresponding to key "+ key +" is null in post parameter");
                sb.append(key + "=" + URLEncoder.encode(value, "UTF-8") + and);
            }
            sb.delete(sb.length()-1, sb.length());
            out.write(sb.toString().getBytes());
            LogManager.e("request:" + host +"?"+sb.toString());
            out.flush();
        }
    }

    private void successResponse(final Object object, final ResultCallback callback)
    {
        mHandler.post(new Runnable()
        {
            @Override
            public void run()
            {
                if (callback != null)
                {
                    callback.onSuccess(object);
                }
            }
        });
    }

    private void failedResponse(final Exception e, final ResultCallback callback)
    {
        mHandler.post(new Runnable()
        {
            @Override
            public void run()
            {
                if (callback != null)
                {
                    ApiException ee = GlobalErrorInflater.getError(e);
                    callback.onError(ee.getMessage()+"-" + ee.getCode(), ee.getCode(), e);
                }
            }
        });
    }

    public void uploadSingleFile(final String urlstr, final Map<String, String> header,
                                 final String upFileParamName, final File files, final boolean isImg,
                                 final ResultCallback<? extends Object> callback)
    {
        uploadSingleFile(urlstr, null, header, upFileParamName, files, isImg, callback);
    }

    public void uploadSingleFile(final String urlstr, final String upFileParamName, final File files,
                                 final boolean isImg, final ResultCallback<? extends Object> callback)
    {
        uploadSingleFile(urlstr, null, null, upFileParamName, files, isImg, callback);
    }

    /***
     * 单文件上传<br/>
     * http文件上传的格式如下:<br/>
     *<br/>
     * -----------------------------7da2e536604c8                           开始分割线<br/>
     *Content-Disposition: form-data; name="username"<br/>
     *<br/>
     *hello word                                                            参数内容，每对key-value占用一套分割线<br/>
     *-----------------------------7da2e536604c8                            分割线<br/>
     *Content-Disposition: form-data; name="file1"; filename="D:/haha.txt"  文件信息<br/>
     *Content-Type: text/plain                                              文件类型<br/>
     *<br/>
     *haha<br/>
     *hahaha                                                                文件内容<br/>
     * -----------------------------7da2e536604c8--                         结束分割线<br/>
     * @param urlstr
     * @param map
     * @param upFileParamName
     * @param files
     * @param isImg
     * @return
     * @throws IOException
     */
    public void uploadSingleFile(final String urlstr, final Map<String, String> map,final Map<String, String> header,
                                 final String upFileParamName, final File files, final boolean isImg,
                                 final ResultCallback<? extends Object> callback)
    {
        final String BOUNDARY = "-"; // 定义数据分隔线
        ThreadManager.getInstance().get()
                .execute(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        HttpURLConnection connection = null;
                        try
                        {
                            connection=
                                    new Builder(urlstr)
                                            .setHeaderMap(header)
                                            .addHeader("Content-Type", "multipart/form-data;boundary=" + BOUNDARY)
//                                            .addHeader("Content-Length", String.valueOf(files.length()))
                                            .build();
                            OutputStream out = new DataOutputStream(connection.getOutputStream());


                            // 根据格式写入post参数
                            writePostParam(map, BOUNDARY, out);

                            //根据格式写入文件
                            writeFile(upFileParamName, files, isImg, BOUNDARY, out);

                            //根据格式写入结束分割线
                            byte[] end_data = ("--"+ BOUNDARY + "--\r\n").getBytes();// 定义最后数据分隔线
                            out.write(end_data);
                            out.flush();
                            out.close();
                            int code = connection.getResponseCode();
                            if (code == HttpURLConnection.HTTP_OK)
                            {
                                InputStream inStream = connection.getInputStream();
                                String json = StreamUtils.read(inStream);
                                handleResponse(json, callback);
                            }else
                            {
                                failedResponse(new ApiException(code, CommonError.HTTP_ERROR_MSG), callback);
                            }
                        }catch (IOException e)
                        {
                            failedResponse(e, callback);
                        }finally
                        {
                            if(connection != null)
                            {
                                connection.disconnect();
                            }
                        }
                    }
                });
    }

    private void writePostParam(Map<String, String> map, String BOUNDARY, OutputStream out) throws IOException
    {
        if (map != null && !map.isEmpty())
        {
            for (Map.Entry<String, String> entry : map.entrySet())
            {
                StringBuilder ssb = new StringBuilder();
                ssb.append("--");
                ssb.append(BOUNDARY);
                ssb.append("\r\n");
                ssb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"");
                ssb.append("\r\n");
                ssb.append("\r\n");
                ssb.append(entry.getValue());
                ssb.append("\r\n");
                byte[] data = ssb.toString().getBytes();
                out.write(data);
            }
        }
    }

    private void writeFile(String upFileParamName, File files, boolean isImg,
                           String BOUNDARY, OutputStream out) throws IOException
    {
        // 文件
        File file = files;

        StringBuilder sb = new StringBuilder();
        sb.append("--");
        sb.append(BOUNDARY);
        sb.append("\r\n");
        sb.append("Content-Disposition: form-data;name=\"" + upFileParamName
                + "\";filename=\"" + files.getName() + "\"\r\n");
        sb.append("Content-Type: "+(isImg ? "image/*\r\n" : "application/octet-stream\r\n"));
        sb.append("Content-Length: "+String.valueOf(file.length() + "\r\n\r\n"));
        byte[] data = sb.toString().getBytes();
        out.write(data);

        DataInputStream in = new DataInputStream(new FileInputStream(file));
        int bytes = 0;
        byte[] bufferOut = new byte[1024];
        while ((bytes = in.read(bufferOut)) > 0)
        {
            out.write(bufferOut, 0, bytes);
            out.flush();
        }
        out.write("\r\n\r\n".getBytes()); // 多个文件时，二个文件之间加入这个
        in.close();
    }
}
